#ifndef XG_HTTPREQUEST_CPP
#define XG_HTTPREQUEST_CPP
////////////////////////////////////////////////////////
#include "../HttpServer.h"
#include "../HttpRequest.h"
#include "../HttpResponse.h"
#include "../../openssl/SSLSocket.h"

void HttpRequest::clear()
{
	hdrsz = 0;
	datsz = 0;
	addsz = 0;
	method = eGET;

	path.clear();
	data.clear();
	node.clear();
	content.free();
	version.clear();
	payload.clear();
	boundary.clear();
}
bool HttpRequest::parseBoundary()
{
	string contype = getHeadValue("Content-Type");

	boundary.clear();

	if (contype.find("multipart/form-data") == string::npos) return true;

	size_t pos = contype.find("boundary");

	if (pos == string::npos) return false;

	const char* str = SkipStartString(contype.c_str() + pos + 8, " \r\t");

	CHECK_FALSE_RETURN(*str == '=');

	str = SkipStartString(str + 1, " \r\t");

	const char* end = strchr(str, ';');

	boundary = (end ? string(str, end) : str);

	return true;
}
bool HttpRequest::parseHeader(const char* msg, int len)
{
	const char* str = msg;
	const char* end = NULL;
	
	CHECK_FALSE_RETURN(len > 8);

	if (memcmp(str, "GET", 3) == 0)
	{
		method = eGET;
		str += 4;
	}
	else if (memcmp(str, "POST", 4) == 0)
	{
		method = ePOST;
		str += 5;
	}
	else if (memcmp(str, "HEAD", 4) == 0)
	{
		method = eHEAD;
		str += 5;
	}
	else if (memcmp(str, "OPTIONS", 7) == 0)
	{
		method = eOPTION;
		str += 8;
	}
	else
	{
		return false;
	}

	while (*str && isspace(*str)) str++;

	end = msg + len - 6;
	
	while (str < end)
	{
		if (isspace(*end))
		{
			path = string(str, end);
			version = string(end + 1, msg + len);

			return true;
		}

		--end;
	}

	return false;
}
string HttpRequest::getHeadString() const
{
	return node.toString();
}
string HttpRequest::getDataString() const
{
	return payload.empty() ? data.toString() : payload;
}
void HttpRequest::setDataString(const string& val)
{
	if (val.length() > 0) data.clear();

	payload = val;
}
bool HttpRequest::setContentType(const string& val)
{
	return setHeadValue("Content-Type", val);
}
string HttpRequest::getCookie(const string& key) const
{
	string cookie = getHeadValue("Cookie");

	if (cookie.empty()) return cookie;

	cookie = ";" + stdx::replace(cookie, "; ", ";");

	size_t pos = cookie.find(";" + key + "=");

	if (pos == string::npos) return stdx::EmptyString();

	cookie = cookie.substr(pos + key.length() + 2);
	pos = cookie.find(';');

	return stdx::trim(pos == string::npos ? cookie : cookie.substr(0, pos));
}
string HttpRequest::getDataValue(const string& key) const
{
	return data.getValue(key);
}
string HttpRequest::getHeadValue(const string& key) const
{
	string val = node.getValue(key);
	
	if (val.length() > 0) return val;

	return node.getValue(stdx::tolower(val = key));
}
int HttpRequest::getParameterKeys(vector<string>& vec) const
{
	vec = data.getKeys();

	return vec.size();
}
bool HttpRequest::parseHeader(const char* msg)
{
	string val;
	const char* str = strstr(msg, "\r\n");

	if (str == NULL || str == msg) return false;

	CHECK_FALSE_RETURN(parseHeader(msg, str - msg));
	CHECK_FALSE_RETURN(node.parse(str + 2));

	path = ParsePath(path, val);

	if (val.length() > 0) setDataString(val, true);

	if (method == eGET) return true;

	return parseBoundary();
}
bool HttpRequest::parse(const char* msg)
{
	const char* end = strstr(msg, HTTP_REQHDR_ENDSTR);

	CHECK_FALSE_RETURN(end && parseHeader(msg));

	end += HTTP_REQHDR_ENDSTR_LEN;

	if (method == ePOST && boundary.empty()) setDataString(end, false);

	return true;
}
int HttpRequest::init(const string& url, bool tiny)
{
	setPath(url);

	if (tiny) return setMethod(eGET) ? XG_OK : XG_DATAERR;

	setHeadValue("Connection", "keep-alive");
	setHeadValue("Cache-Control", "no-cache");
	setHeadValue("Accept-Language", "zh-CN,zh;q=0.8");
	setHeadValue("Accept-Encoding", "gzip,deflate,sdch");

	setHeadValue("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");

	string ua = GetUserAgent();

	if (ua.empty()) SetUserAgent(ua = "Chrome/50.0 Mozilla/5.0");

	setHeadValue("User-Agent", ua);

	return setMethod(eGET) ? XG_OK : XG_DATAERR;
}
int HttpRequest::init(const HttpFrame& frame)
{
	const auto& head = frame.getHead();

	method = frame.getMethod();
	content = frame.getData();
	hdrsz = frame.hdrsz;
	datsz = frame.datsz;
	version = "HTTP/2";
	path = frame.path;
	node = head;

	if (content.size() < hdrsz + datsz) return XG_DATAERR;

	if (method == eGET)
	{
		if (frame.param.length() > 0) setDataString(frame.param, true);
	}
	else
	{
		parseBoundary();

		if (datsz > 0 && boundary.empty()) setDataString(content.str() + hdrsz, true);
	}

	return XG_OK;
}
int HttpRequest::init(Socket* sock, SmartBuffer data, int& readed)
{
	int len = 0;
	char* buffer = data.str();
	const int maxsz = data.size();

	if ((len = maxsz - readed) <= 0) return XG_DATAERR;

	if (hdrsz > 0)
	{
		if ((len = min(addsz, len)) <= 0) return XG_DATAERR;

		if ((len = sock->read(buffer + readed, len, false)) < 0) return len;
	
		if (len == 0) return XG_TIMEOUT;

		readed += len;
	}
	else
	{
		if ((len = sock->peek(buffer + readed, len)) < 0) return len;

		if (len == 0) return XG_TIMEOUT;

		buffer[readed + len] = 0;
		
		char* end = strstr(buffer, HTTP_REQHDR_ENDSTR);

		if (end == NULL)
		{
			if (readed + len >= maxsz) return XG_DATAERR;

			if (sock->read(buffer + readed, len, false) < len) return XG_NETERR;

			readed += len;

			return XG_TIMEOUT;
		}

		*end = 0;

		if (!parseHeader(buffer)) return XG_DATAERR;

		*end = *HTTP_REQHDR_ENDSTR;
		end += HTTP_REQHDR_ENDSTR_LEN;
		
		if (method == ePOST)
		{
			string str = getHeadValue("Content-Length");

			if (str.length() > 0 && (datsz = stdx::atoi(str.c_str())) < 0) return XG_DATAERR;
		}

		hdrsz = end - buffer;

		if (readed + len > hdrsz + datsz) len = hdrsz + datsz - readed;

		if (sock->read(buffer + readed, len, false) < len) return XG_NETERR;

		readed += len;
	}

	if ((addsz = hdrsz + datsz - readed) > 0)
	{
		if (readed < maxsz) return XG_TIMEOUT;
	}

	if (addsz == 0 && datsz > 0 && boundary.empty())
	{
		setDataString(buffer + hdrsz, false);
	}

	data.truncate(readed);
	content = data;

	return readed;
}
string HttpRequest::toString() const
{
	SmartBuffer data = toBuffer();

	return data.isNull() ? stdx::EmptyString() : string(data.str(), data.str() + data.size());
}
SmartBuffer HttpRequest::toBuffer() const
{
	string str;
	string msg;
	SmartBuffer data;

	if (method == eGET)
	{
		str = "GET /" + stdx::EncodeURL(path);
	}
	else if (method == ePOST)
	{
		str = "POST /" + stdx::EncodeURL(path);
	}
	else if (method == eHEAD)
	{
		str = "HEAD /" + stdx::EncodeURL(path);
	}
	else if (method == eOPTION)
	{
		str = "OPTIONS /" + stdx::EncodeURL(path);
	}
	else
	{
		return content;
	}

	if (method == ePOST)
	{
		int len = content.size() - hdrsz;

		if (len == datsz && hdrsz > 0)
		{
			if (len > 0) memcpy(data.malloc(len), content.str() + hdrsz, len);
		}
		else
		{
			msg = getDataString();

			const_cast<HttpRequest*>(this)->setHeadValue("Content-Length", stdx::str(msg.length()));
		}
	}
	else
	{
		msg = getDataString();

		if (msg.length() > 0)
		{
			str += "?";
			str += msg;
			msg.clear();
		}
	}

	str += " ";
	str += version;
	str += node.getEndSpliter();
	str += node.toString();
	str += node.getEndSpliter();
	str += node.getEndSpliter();

	if (data.size() > 0)
	{
		int len = str.length();
		SmartBuffer content(len + data.size());

		memcpy(content.str(), str.c_str(), len);
		memcpy(content.str() + len, data.str(), data.size());

		return content;
	}

	if (msg.length() > 0) str += msg;

	return data = str;
}
bool HttpRequest::setPath(const string& url)
{
	if (url == "/")
	{
		path = url;

		return true;
	}

	string val;

	path = ParsePath(url, val);

	return val.empty() ? true : setDataString(val, true);
}
bool HttpRequest::setCookie(const string& val)
{
	return setHeadValue("Cookie", val);
}
bool HttpRequest::setVersion(const string& version)
{
	this->version = version;

	return true;
}
bool HttpRequest::setDataString(const string& val, bool inited)
{
	data.parse(val, inited);

	payload = val;

	return true;
}
bool HttpRequest::setParameter(const string& key, const string& val)
{
	return setDataValue(key, val);
}
bool HttpRequest::setDataValue(const string& key, const string& val)
{
	payload.clear();

	return data.setValue(key, val);
}
bool HttpRequest::setHeadValue(const string& key, const string& val)
{
	node[key] = val;

	return true;
}
string HttpRequest::ParsePath(const string& path, string& param)
{
	string url = path;
	size_t pos = url.find('?');

	if (pos == string::npos)
	{
		pos = url.find('#');
	}
	else
	{
		size_t idx = url.find('#');

		if (idx == string::npos)
		{
			param = url.substr(pos + 1);
		}
		else
		{
			param = url.substr(pos + 1, idx);
		}
	}

	if (pos != string::npos) url = url.substr(0, pos);

	url = stdx::trim(url, HTTP_SPACELIST);

	return url.empty() ? "index" : stdx::DecodeURL(url);
}
bool HttpRequest::GetInfoFromURL(const string& url, string& host, string& path, string& ip, int& port, bool& crypted)
{
	const char* end = NULL;
	const char* str = url.c_str();

	crypted = false;
	path.clear();
	host.clear();
	port = 0;

	if (strncmp(str, "http://", 7) == 0 || strncmp(str, "HTTP://", 7) == 0)
	{
		crypted = false;
		str = str + 7;
	}
	else if (strncmp(str, "https://", 8) == 0 || strncmp(str, "HTTPS://", 8) == 0)
	{
		crypted = true;
		str = str + 8;
	}

	end = strchr(str, ':');

	if (end)
	{
		host = string(str, end++);

		if (host.find('?') == string::npos)
		{
			path = SkipStartString(end, "0123456789");
			port = stdx::atoi(end);
		}
		else
		{
			host.clear();
		}
	}

	if (host.empty())
	{
		if ((end = strchr(str, '/')) || (end = strchr(str, '?')))
		{
			host = string(str, end);
			path = end;
		}
		else
		{
			host = str;
		}
	}

	if (port <= 0) port = crypted ? 443 : 80;
	if (path.empty() || path[0] == '?') path = "/" + path;

	ip = Socket::GetHostAddress(host);

	return ip.length() > 0;
}
SmartBuffer HttpRequest::getResult(sp<Socket> sock, bool decode, int timeout) const
{
	sp<HttpResponse> response = getResponse(sock, decode, timeout);

	return response ? response->getResult() : SmartBuffer();
}
sp<HttpResponse> HttpRequest::getResponse(sp<Socket> sock, bool decode, int timeout) const
{
	if (!sock) return NULL;

	SmartBuffer data = toBuffer();

	if (sock->write(data.str(), data.size()) < 0)
	{
		sock->close();

		return NULL;
	}

	sp<HttpResponse> response = newsp<HttpResponse>();

	if (response->init(sock, decode, timeout) < 0)
	{
		sock->close();

		return NULL;
	}

	if (response->isKeepAlive()) return response;

	sock->close();

	return response;
}
SmartBuffer HttpRequest::getResult(const string& host, int port, bool crypted, bool decode, int timeout) const
{
	sp<HttpResponse> response = getResponse(host, port, crypted, decode, timeout);

	return response ? response->getResult() : SmartBuffer();
}
sp<HttpResponse> HttpRequest::getResponse(const string& host, int port, bool crypted, bool decode, int timeout) const
{
	string ip = Socket::GetHostAddress(host);

	return getResponse(crypted ? SSLSocketPool::Connect(ip, port) : SocketPool::Connect(ip, port), decode, timeout);
}
////////////////////////////////////////////////////////
#endif